package club.jdiy.utils.reflex;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 包扫描工具类.
 *
 * @author ziquee(子秋) QQ号:39886616 主页: http://www.jdiy.club
 * @version 20200517
 */
public class PackageScan {
    /*public static void main(String[] args) {
        Set<Class<?>> sets = getClasses("org", clazz -> clazz != FormHandler.class && FormHandler.class.isAssignableFrom(clazz));
        for (Class<?> c : sets) {
            System.out.println(c.getName() + "    " + c.getSimpleName());
        }
    }*/

    public interface MatchFilter {
        boolean match(Class<?> clzz);
    }

    /**
     * 从包package中获取所有的Class
     *
     * @param pack 包名. 如： club.jdiy.core pack="."(或/或空字符串)表示扫描整个类路径
     * @return Set
     */
    public static Set<Class<?>> getClasses(String pack, PackageScan.MatchFilter filter) {
        Set<Class<?>> classes = new LinkedHashSet<>();
        String packageDirName = pack.replace('.', '/');
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                String protocol = url.getProtocol();
                if ("file".equals(protocol)) {//in dir
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    findAndAddClassesInPackageByFile(pack, filePath, classes, filter);
                } else if ("jar".equals(protocol)) {//in jar
                    scanPackage(url, classes, filter);
                }
            }
        } catch (Throwable ignore) {
        }

        return classes;
    }


    //扫描jar包方法。
    private static void scanPackage(URL url, Set<Class<?>> classes, PackageScan.MatchFilter filter) throws IOException {
        JarURLConnection urlConnection = (JarURLConnection) url.openConnection();
        JarFile jarfile = urlConnection.getJarFile();
        Enumeration<JarEntry> jarEntries = jarfile.entries();
        while (jarEntries.hasMoreElements()) {
            JarEntry jarEntry = jarEntries.nextElement();
            String jarName = jarEntry.getName();
            if (!jarName.endsWith(".class")) {
                continue;
            }
            String className = jarName.replace(".class", "").replaceAll("/", ".");
            try {
                Class<?> klass = Thread.currentThread().getContextClassLoader().loadClass(className);
                if (klass.isAnnotation()
                        || klass.isInterface()
                        || klass.isEnum()
                        || klass.isPrimitive()) {
                    continue;
                }
                if (filter == null || filter.match(klass)) classes.add(klass);
            } catch (Throwable ignore) {
            }
        }
    }

    private static void findAndAddClassesInPackageByFile(String packageName,
                                                         String packagePath,
                                                         Set<Class<?>> classes, MatchFilter filter) {
        File dir = new File(packagePath);
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }

        File[] dirfiles = dir.listFiles(file -> file.isDirectory() || (file.getName().endsWith(".class")));
        if (dirfiles == null) return;
        for (File file : dirfiles) {
            boolean isRoot = "".equals(packageName) || ".".equals(packageName) || "/".equals(packageName);
            if (file.isDirectory()) {
                String fn = isRoot ? file.getName() : packageName + "." + file.getName();
                findAndAddClassesInPackageByFile(fn, file.getAbsolutePath(), classes, filter);
            } else {
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    String fn = isRoot ? className : packageName + "." + className;
                    Class<?> c = Thread.currentThread().getContextClassLoader().loadClass(fn);
                    if (filter == null || filter.match(c)) classes.add(c);
                } catch (Throwable ignore) {
                }
            }
        }
    }
}
